package config

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"regexp"
	"strings"

	ini "github.com/ochinchina/go-ini"
	"github.com/ochinchina/supervisord/tool"
	log "github.com/sirupsen/logrus"
)

//
// Load load the configuration and return the loaded programs
func (c *Config) Load() ([]string, error) {
	// myini := ini.NewIni()
	// c.ProgramGroup = NewProcessGroup()
	// log.WithFields(log.Fields{"file": c.configFile}).Info("load configuration from file")
	// myini.LoadFile(c.configFile)

	// includeFiles := c.getIncludeFiles(myini)
	// for _, f := range includeFiles {
	// 	log.WithFields(log.Fields{"file": f}).Info("load configuration from file")
	// 	myini.LoadFile(f)
	// }

	// ret := c.parse(myini)

	// for _, en := range c.entries {
	// 	fmt.Println("------------------" + en.Name)
	// 	for k, v := range en.keyValues {
	// 		fmt.Println(k + " = " + v)
	// 	}
	// }

	// if c.configFile != "" {
	// 	os.Exit(1)
	// }

	// return ret, nil
	return c.Load2(c.configFile)
}

func (c *Config) getIncludeFiles(cfg *ini.Ini) []string {
	result := make([]string, 0)
	if includeSection, err := cfg.GetSection("include"); err == nil {
		key, err := includeSection.GetValue("files")
		if err == nil {
			env := tool.NewStringExpression("here", c.GetConfigFileDir())
			files := strings.Fields(key)
			for _, fRaw := range files {
				dir := c.GetConfigFileDir()
				f, err := env.Eval(fRaw)
				if err != nil {
					continue
				}
				if filepath.IsAbs(f) {
					dir = filepath.Dir(f)
				} else {
					dir = filepath.Join(c.GetConfigFileDir(), filepath.Dir(f))
				}
				fileInfos, err := ioutil.ReadDir(dir)
				if err == nil {
					goPattern := toRegexp(filepath.Base(f))
					for _, fileInfo := range fileInfos {
						if matched, err := regexp.MatchString(goPattern, fileInfo.Name()); matched && err == nil {
							result = append(result, filepath.Join(dir, fileInfo.Name()))
						}
					}
				}

			}
		}
	}
	return result

}

func (c *Config) parse(cfg *ini.Ini) []string {
	c.setProgramDefaultParams(cfg)
	c.parseGroup(cfg)
	loadedPrograms := c.parseProgram(cfg)

	// parse non-group,non-program and non-eventlistener sections
	for _, section := range cfg.Sections() {
		if !strings.HasPrefix(section.Name, "group:") && !strings.HasPrefix(section.Name, "program:") && !strings.HasPrefix(section.Name, "eventlistener:") {
			entry := c.createEntry(section.Name, c.GetConfigFileDir())
			c.entries[section.Name] = entry
			entry.parse(section)
		}
	}
	return loadedPrograms
}

// create a new entry or return the already-exist entry
func (c *Config) createEntry(name string, configDir string) *Entry {
	entry, ok := c.entries[name]

	if !ok {
		entry = NewEntry(configDir)
		c.entries[name] = entry
	}
	return entry
}

// set the default parameters of programs
func (c *Config) setProgramDefaultParams(cfg *ini.Ini) {
	program_default_section, err := cfg.GetSection("program-default")
	if err == nil {
		for _, section := range cfg.Sections() {
			if section.Name == "program-default" || !strings.HasPrefix(section.Name, "program:") {
				continue
			}
			for _, key := range program_default_section.Keys() {
				if !section.HasKey(key.Name()) {
					section.Add(key.Name(), key.ValueWithDefault(""))
				}
			}

		}
	}
}

// convert supervisor file pattern to the go regrexp
func toRegexp(pattern string) string {
	tmp := strings.Split(pattern, ".")
	for i, t := range tmp {
		s := strings.Replace(t, "*", ".*", -1)
		tmp[i] = strings.Replace(s, "?", ".", -1)
	}
	return strings.Join(tmp, "\\.")
}

func (c *Entry) setGroup(group string) {
	c.Group = group
}

func (c *Entry) parse(section *ini.Section) {
	c.Name = section.Name
	for _, key := range section.Keys() {
		c.keyValues[key.Name()] = strings.TrimSpace(key.ValueWithDefault(""))
	}
}

func (c *Config) parseGroup(cfg *ini.Ini) {

	// parse the group at first
	for _, section := range cfg.Sections() {
		if strings.HasPrefix(section.Name, "group:") {
			entry := c.createEntry(section.Name, c.GetConfigFileDir())
			entry.parse(section)
			groupName := entry.GetGroupName()
			programs := entry.GetPrograms()
			for _, program := range programs {
				c.ProgramGroup.Add(groupName, program)
			}
		}
	}
}

func (c *Config) isProgramOrEventListener(section *ini.Section) (bool, string) {
	// check if it is a program or event listener section
	isProgram := strings.HasPrefix(section.Name, "program:")
	isEventListener := strings.HasPrefix(section.Name, "eventlistener:")
	prefix := ""
	if isProgram {
		prefix = "program:"
	} else if isEventListener {
		prefix = "eventlistener:"
	}
	return isProgram || isEventListener, prefix
}

// parse the sections starts with "program:" prefix.
//
// Return all the parsed program names in the ini
func (c *Config) parseProgram(cfg *ini.Ini) []string {
	loadedPrograms := make([]string, 0)
	for _, section := range cfg.Sections() {
		programOrEventListener, prefix := c.isProgramOrEventListener(section)

		// if it is program or event listener
		if programOrEventListener {
			// get the number of processes
			numProcs, err := section.GetInt("numprocs")
			programName := section.Name[len(prefix):]
			if err != nil {
				numProcs = 1
			}
			procName, err := section.GetValue("process_name")
			if numProcs > 1 {
				if err != nil || strings.Index(procName, "%(process_num)") == -1 {
					log.WithFields(log.Fields{
						"numprocs":     numProcs,
						"process_name": procName,
					}).Error("no process_num in process name")
				}
			}
			originalProcName := programName
			if err == nil {
				originalProcName = procName
			}

			originalCmd := section.GetValueWithDefault("command", "")

			for i := 1; i <= numProcs; i++ {
				envs := tool.NewStringExpression("program_name", programName,
					"process_num", fmt.Sprintf("%d", i),
					"group_name", c.ProgramGroup.GetGroup(programName, programName),
					"here", c.GetConfigFileDir())
				envValue, err := section.GetValue("environment")
				if err == nil {
					for k, v := range *parseEnv(envValue) {
						envs.Add(fmt.Sprintf("ENV_%s", k), v)
					}
				}
				cmd, err := envs.Eval(originalCmd)
				if err != nil {
					log.WithFields(log.Fields{
						log.ErrorKey: err,
						"program":    programName,
					}).Error("get envs failed")
					continue
				}
				section.Add("command", cmd)

				procName, err := envs.Eval(originalProcName)
				if err != nil {
					log.WithFields(log.Fields{
						log.ErrorKey: err,
						"program":    programName,
					}).Error("get envs failed")
					continue
				}

				section.Add("process_name", procName)
				section.Add("numprocs_start", fmt.Sprintf("%d", (i-1)))
				section.Add("process_num", fmt.Sprintf("%d", i))
				entry := c.createEntry(procName, c.GetConfigFileDir())
				entry.parse(section)
				entry.Name = prefix + procName
				group := c.ProgramGroup.GetGroup(programName, programName)
				entry.Group = group
				loadedPrograms = append(loadedPrograms, procName)
			}
		}
	}
	return loadedPrograms
}
